﻿#include "FITKOCCGraphObjectModel.h"

// OCC
#include <TopExp.hxx>
#include <TopoDS_Shape.hxx>
#include <AIS_DisplayMode.hxx>
#include <Prs3d_Drawer.hxx>
#include <Prs3d_ShadingAspect.hxx>
#include <Prs3d_LineAspect.hxx>
#include <Prs3d_PointAspect.hxx>
#include <AIS_InteractiveContext.hxx>
#include <TopTools_IndexedMapOfShape.hxx>

// Graph
#include "FITKOCCAIS_ColoredShape.h"

// Data
#include "FITK_Interface/FITKInterfaceGeometry/FITKAbsGeoCommand.h"
#include "FITK_Interface/FITKInterfaceGeometry/FITKGeoCommandList.h"
#include "FITK_Component/FITKGeoCompOCC/FITKAbstractOCCModel.h"

// Test
//@{
#include <QStandardPaths>
#include <QFileDialog>
#include <BRepBuilderAPI_MakeShape.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <STEPControl_Reader.hxx>
#include <IGESControl_Reader.hxx>
#include <StlAPI.hxx>
//@}

namespace Exchange
{
    /**
     * @brief   Internal struct for getting the index of child data from root shape.
     * @author  ChengHaotian (yeguangbaozi@foxmail.com)
     * @date    2024-06-13
     */
    struct _ShapeIndiceMap
    {
        TopTools_IndexedMapOfShape iVert;
        TopTools_IndexedMapOfShape iEdge;
        TopTools_IndexedMapOfShape iWire;
        TopTools_IndexedMapOfShape iFace;
        TopTools_IndexedMapOfShape iShell;
        TopTools_IndexedMapOfShape iSolid;
        TopTools_IndexedMapOfShape iCompSolid;
        TopTools_IndexedMapOfShape iComPound;
    };

    FITKOCCGraphObjectModel::FITKOCCGraphObjectModel(Interface::FITKAbsGeoCommand* modelData)
        : FITKOCCGraphObject3D(modelData)
    {
        if (!modelData)
        {
            return;
        }

        // Initialize.
        bool flag = initShapeData();
        if (!flag)
        {
            return;
        }

        // Generate the interactive objects.
        generateShape();

        // Save the data type.
        m_graphInfo.Type = FITKOCCCommons::ModelGraph;

        // Save the fixed size flag.
        m_hasFixedSize = true;
    }

    FITKOCCGraphObjectModel::~FITKOCCGraphObjectModel()
    {
        // Delete indice map.
        deleteObj(m_indiceMap);

        // Don't need to delete pointers here.
        m_fAisShape = nullptr;
        m_tpShape = nullptr;
    }

    bool FITKOCCGraphObjectModel::initShapeData()
    {
        Interface::FITKAbsGeoCommand* modelData = dynamic_cast<Interface::FITKAbsGeoCommand*>(_dataObj);
        if (!modelData)
        {
            return false;
        }

        OCC::FITKAbstractOCCModel* modelOCC = modelData->getTShapeAgent<OCC::FITKAbstractOCCModel>();
        if (!modelOCC)
        {
            return false;
        }

        // Get the model shape.( The sketch don't need to check the shape data. )
        m_tpShape = modelOCC->getShape();
        if (!m_tpShape)
        {
            return false;
        }

        // Create shape indice map.
        m_indiceMap = new _ShapeIndiceMap;

        if (m_useIndexMap)
        {
            initShapeIndiceMap(*m_tpShape, m_indiceMap);
        }

        return true;
    }

    void FITKOCCGraphObjectModel::initShapeIndiceMap(TopoDS_Shape& shape, _ShapeIndiceMap* indiceMap)
    {
        // Check the shape first.
        if (shape.IsNull())
        {
            return;
        }

        // Initialize maps.
        TopExp::MapShapes(shape, TopAbs_VERTEX, indiceMap->iVert);
        TopExp::MapShapes(shape, TopAbs_EDGE, indiceMap->iEdge);
        // TopExp::MapShapes(shape, TopAbs_WIRE, indiceMap->iWire);
        // TopExp::MapShapes(shape, TopAbs_SHELL, indiceMap->iShell);
        TopExp::MapShapes(shape, TopAbs_FACE, indiceMap->iFace);
        TopExp::MapShapes(shape, TopAbs_SOLID, indiceMap->iSolid);
        TopExp::MapShapes(shape, TopAbs_COMPSOLID, indiceMap->iCompSolid);
        // TopExp::MapShapes(shape, TopAbs_COMPOUND, indiceMap->iComPound);
    }

    bool FITKOCCGraphObjectModel::getDataVisibility()
    {
        // Get the command status first.
        Interface::FITKAbsGeoCommand* cmd = dynamic_cast<Interface::FITKAbsGeoCommand*>(_dataObj);
        if (!cmd)
        {
            return false;
        }

        // Then check if the command is in the geometry command( undo ) list.
        bool visible = cmd->getEnableReferenceList();

        // The modified and deleted command must be hidden.
        visible &= cmd->getCommandStatus() != Interface::FITKGeoEnum::FITKGeoStatus::FGSDeleted && 
            cmd->getCommandStatus() != Interface::FITKGeoEnum::FITKGeoStatus::FGSModified;

        // Check the reference count.
        visible &= (cmd->getReferencedCmdCount() == 0);

        // Get the data visibility.
        visible &= FITKOCCGraphObject3D::getDataVisibility();

        return visible;
    }

    void FITKOCCGraphObjectModel::update(bool forceUpdate)
    {
        if (!m_fAisShape)
        {
            return;
        }

        Interface::FITKAbsGeoCommand* modelData = dynamic_cast<Interface::FITKAbsGeoCommand*>(_dataObj);
        if (!modelData)
        {
            return;
        }

        OCC::FITKAbstractOCCModel* modelOCC = modelData->getTShapeAgent<OCC::FITKAbstractOCCModel>();
        if (!modelOCC)
        {
            return;
        }

        // Update the visibility first.
        updateVisibility();

        // Check if the shape has been changed.
        TopoDS_Shape* newShape = modelOCC->getShape();
        if (!newShape)
        {
            return;
        }

        if (newShape->HashCode(INT_MAX) != m_hashCode || forceUpdate)
        {
            // Reset the shape.
            m_tpShape = newShape;
            m_fAisShape->SetShape(*m_tpShape);
            m_fAisShape->SetToUpdate();

            // Save the hash code.
            m_hashCode = m_tpShape->HashCode(INT_MAX);

            // Update interactive object if necessary.
            updateInteractiveObjs();
        }

        // Update base.
        FITKOCCGraphObject3D::update(forceUpdate);
    }

    void FITKOCCGraphObjectModel::updateInteractiveObjs()
    {
        if (m_context && m_fAisShape)
        {
            // Check if the interactive object is visible.
            if (m_context->DisplayStatus(m_fAisShape) == AIS_DisplayStatus::AIS_DS_Displayed)
            {
                m_context->Redisplay(m_fAisShape, true);
            }
        }
    }

    int FITKOCCGraphObjectModel::getShapeIndex(FITKOCCCommons::ShapeMeshType type, TopoDS_Shape & shape)
    {
        if (!m_indiceMap || !m_useIndexMap)
        {
            return -1;
        }

        int index = -1;

        switch (type)
        {
        case Exchange::FITKOCCCommons::SMT_ShapeVertex:
            index = m_indiceMap->iVert.FindIndex(shape);
            break;
        case Exchange::FITKOCCCommons::SMT_ShapeEdge:
            index = m_indiceMap->iEdge.FindIndex(shape);
            break;
        case Exchange::FITKOCCCommons::SMT_ShapeFace:
            index = m_indiceMap->iFace.FindIndex(shape);
            break;
        case Exchange::FITKOCCCommons::SMT_ShapeSolid:
            index = m_indiceMap->iSolid.FindIndex(shape);
            break;
        case Exchange::FITKOCCCommons::SMT_TypeNone:
        case Exchange::FITKOCCCommons::SMT_MeshNode:
        case Exchange::FITKOCCCommons::SMT_MeshElement:
        default:
            return -1;
        }

        if (index <= 0)
        {
            index = -1;
        }

        return index;
    }

    TopoDS_Shape FITKOCCGraphObjectModel::getIndexedShape(FITKOCCCommons::ShapeMeshType type, int index)
    {
        if (!m_indiceMap || !m_useIndexMap)
        {
            return TopoDS_Shape();
        }

        switch (type)
        {
        case Exchange::FITKOCCCommons::SMT_ShapeVertex:
            return m_indiceMap->iVert.FindKey(index);
            break;
        case Exchange::FITKOCCCommons::SMT_ShapeEdge:
            return m_indiceMap->iEdge.FindKey(index);
            break;
        case Exchange::FITKOCCCommons::SMT_ShapeFace:
            return m_indiceMap->iFace.FindKey(index);
            break;
        case Exchange::FITKOCCCommons::SMT_ShapeSolid:
            return m_indiceMap->iSolid.FindKey(index);
            break;
        case Exchange::FITKOCCCommons::SMT_TypeNone:
        case Exchange::FITKOCCCommons::SMT_MeshNode:
        case Exchange::FITKOCCCommons::SMT_MeshElement:
        default:
            return TopoDS_Shape();
        }
    }

    void FITKOCCGraphObjectModel::setViewMode(FITKOCCCommons::ShapeMeshViewMode type, bool visible)
    {
        if (!m_fAisShape)
        {
            return;
        }

        // Properties.
        Handle(Prs3d_Drawer) properties = m_fAisShape->Attributes();

        switch (type)
        {
        case FITKOCCCommons::SMVM_Shade:
        {
            // Show or hide face with the transparency.
            properties->ShadingAspect()->SetTransparency(visible ? 0. : 1.);
            break;
        }
        case FITKOCCCommons::SMVM_Wireframe:
        {
            // Show or hide line with the type of line.
            properties->LineAspect()->SetTypeOfLine(visible ? Aspect_TypeOfLine::Aspect_TOL_SOLID : Aspect_TypeOfLine::Aspect_TOL_EMPTY);
            break;
        }
        case FITKOCCCommons::SMVM_Vertex:
        {
            // Show or hide vertex with the type of marker( point ).
            properties->PointAspect()->SetTypeOfMarker(visible ? Aspect_TypeOfMarker::Aspect_TOM_POINT : Aspect_TypeOfMarker::Aspect_TOM_EMPTY);
            break;
        }
        default:
            break;
        }

        // Save the visibility.
        FITKOCCGraphObject3D::setViewMode(type, visible);
    }

    void FITKOCCGraphObjectModel::setColor(QColor color, FITKOCCCommons::ShapeMeshType type, int index)
    {
        if (!m_indiceMap || !m_fAisShape || !color.isValid() || !m_useIndexMap)
        {
            return;
        }

        // Get the shape.
        TopoDS_Shape shape;

        // Lambda for checking whether the index is valid in map.
        auto checkMap = [=](TopTools_IndexedMapOfShape & map, int index, TopoDS_Shape & shape)
        {
            // Check the index.
            if (index < 1 || index > map.Size())
            {
                return false;
            }

            // Get the shape by index.
            shape = map.FindKey(index);

            return true;
        };

        bool isValid = false;

        switch (type)
        {
        case FITKOCCCommons::SMT_ShapeVertex:
        {
            isValid = checkMap(m_indiceMap->iVert, index, shape);
            break;
        }
        case FITKOCCCommons::SMT_ShapeEdge:
        {
            isValid = checkMap(m_indiceMap->iEdge, index, shape);
            break;
        }
        case FITKOCCCommons::SMT_ShapeFace:
        {
            isValid = checkMap(m_indiceMap->iFace, index, shape);
            break;
        }
        case FITKOCCCommons::SMT_ShapeSolid:
        {
            isValid = checkMap(m_indiceMap->iSolid, index, shape);
            break;
        }
        case FITKOCCCommons::SMT_TypeNone:
        default:
            return;
        }

        // Check the shape.
        if (!isValid || shape.IsNull())
        {
            return;
        }

        // Set the color for the shape.
        m_fAisShape->SetCustomColor(shape, FITKOCCCommons::QColorToQuan(color));
    }

    void FITKOCCGraphObjectModel::setColor(QColor color, FITKOCCCommons::ShapeMeshType type, QList<int> indice)
    {
        if (!m_indiceMap || !m_fAisShape || !color.isValid() || !m_useIndexMap)
        {
            return;
        }

        // Lambda for checking whether the index is valid in map and change the color at the same time.
        auto changeColor = [=](TopTools_IndexedMapOfShape & map, QList<int> & indice, QColor color, AIS_ColoredShape* aisShape)
        {
            Quantity_Color occColor = FITKOCCCommons::QColorToQuan(color);

            for (const int & index : indice)
            {
                // Check the index.
                if (index < 1 || index > map.Size())
                {
                    continue;
                }

                // Get the shape by index.
                const TopoDS_Shape & shape = map.FindKey(index);
                if (shape.IsNull())
                {
                    continue;
                }

                // Set the color for the shape.
                aisShape->SetCustomColor(shape, occColor);
            }
        };

        switch (type)
        {
        case FITKOCCCommons::SMT_ShapeVertex:
        {
            changeColor(m_indiceMap->iVert, indice, color, m_fAisShape);
            break;
        }
        case FITKOCCCommons::SMT_ShapeEdge:
        {
            changeColor(m_indiceMap->iEdge, indice, color, m_fAisShape);
            break;
        }
        case FITKOCCCommons::SMT_ShapeFace:
        {
            changeColor(m_indiceMap->iFace, indice, color, m_fAisShape);
            break;
        }
        case FITKOCCCommons::SMT_ShapeSolid:
        {
            changeColor(m_indiceMap->iSolid, indice, color, m_fAisShape);
            break;
        }
        case FITKOCCCommons::SMT_TypeNone:
        default:
            return;
        }
    }

    void FITKOCCGraphObjectModel::setColor(QColor color)
    {
        if (!m_fAisShape || !color.isValid())
        {
            return;
        }

        // OCC color.
        Quantity_Color quanColor = FITKOCCCommons::QColorToQuan(color);

        // Initialize line default color.
        m_fAisShape->SetColor(quanColor);

        // Get the properties.
        Handle(Prs3d_Drawer) properties = m_fAisShape->Attributes();

        // Face properties.
        properties->ShadingAspect()->SetColor(quanColor); 

        // Line properties.
        properties->WireAspect()->SetColor(quanColor);
        properties->LineAspect()->SetColor(quanColor);

        // Face-line properties.
        properties->FaceBoundaryAspect()->SetColor(quanColor);

        // Point properties.
        properties->PointAspect()->SetColor(quanColor);
    }

    void FITKOCCGraphObjectModel::setEnableTransparency(bool isOn)
    {
        if (!m_fAisShape)
        {
            return;
        }

        m_fAisShape->SetTransparency(isOn ? FITKOCCCommons::s_transpacency : 0.);
    }

    void FITKOCCGraphObjectModel::setPointMark(FITKOCCCommons::PointMarkShape type)
    {
        if (!m_fAisShape)
        {
            return;
        }

        // Get the properties.
        Handle(Prs3d_Drawer) properties = m_fAisShape->Attributes();

        // Point properties.
        properties->PointAspect()->SetTypeOfMarker(Aspect_TypeOfMarker(type));
    }

    void FITKOCCGraphObjectModel::setPointSizeScale(double factor)
    {
        if (!m_fAisShape || factor <= 0)
        {
            return;
        }

        // Get the properties.
        Handle(Prs3d_Drawer) properties = m_fAisShape->Attributes();

        // Get the point aspect.
        properties->PointAspect()->SetScale(factor);
        m_fAisShape->SetAttributes(properties);
    }

    void FITKOCCGraphObjectModel::setLineWidth(double size)
    {
        if (!m_fAisShape)
        {
            return;
        }

        // Get the properties.
        Handle(Prs3d_Drawer) properties = m_fAisShape->Attributes();

        // Get the line aspect.
        properties->LineAspect()->SetWidth(size);
    }

    void FITKOCCGraphObjectModel::highlight(bool update)
    {
        if (!m_context || !m_fAisShape || m_isHighlighting)
        {
            return;
        }

        // Highlight the whole object.
        m_context->HilightWithColor(m_fAisShape, m_context->HighlightStyle(Prs3d_TypeOfHighlight::Prs3d_TypeOfHighlight_LocalSelected), update);

        // Save the highlight flag.
        FITKOCCGraphObject3D::highlight(update);
    }

    void FITKOCCGraphObjectModel::disHighlight(bool update)
    {
        if (!m_context || !m_fAisShape || !m_isHighlighting)
        {
            return;
        }

        // Dis-highlight the whole object.
        m_context->Unhilight(m_fAisShape, update);

        // Save the highlight flag.
        FITKOCCGraphObject3D::disHighlight(update);
    }

    void FITKOCCGraphObjectModel::setVisible(bool visibility)
    {
        if (!m_fAisShape || !m_context)
        {
            return;
        }

        if (visibility)
        {
            // Show the object.
            m_context->Display(m_fAisShape, true);
        }
        else
        {
            // Dishighight first.
            disHighlight();

            // Hide the object.
            m_context->Erase(m_fAisShape, true);
        }
    }

    void FITKOCCGraphObjectModel::addSelectMode(CommonOCC::SelectMode mode, bool force)
    {
        // Clear select modes.
        if (mode == CommonOCC::SelectMode::S_None)
        {
            clearSelectMode();
            return;
        }

        // Check the shape data.
        if (!m_tpShape || !m_context)
        {
            return;
        }

        // Check the select mode.
        CommonOCC::SelectMode realMode = CommonOCC::SelectMode::S_None;
        if (m_supportedSelectModeHash[mode] || force)
        {
            realMode = mode;
        }

        // Deal with the compound select mode.
        //@{
        if (realMode == CommonOCC::SelectMode::S_ShapeCompound &&
            m_tpShape->ShapeType() != TopAbs_ShapeEnum::TopAbs_COMPOUND)
        {
            realMode = CommonOCC::SelectMode::S_None;
        }
        //@}

        if (m_currSelectModes.contains(realMode))
        {
            return;
        }

        // Save the mode.
        if (realMode != CommonOCC::SelectMode::S_None)
        {
            m_currSelectModes.push_back(realMode);
        }

        // Set all objects'select mode by default.
        for (const opencascade::handle<AIS_InteractiveObject> & obj : m_objs)
        {
            if (!obj)
            {
                continue;
            }

            if (realMode == CommonOCC::SelectMode::S_None)
            {
                m_context->Deactivate(obj);
            }
            else
            {
                // Check if the object is visible.
                if (getVisibility())
                {
                    m_context->Load(obj, -1);
                    m_context->Activate(obj, realMode, true);
                }
            }
        }
    }

    bool FITKOCCGraphObjectModel::test(QVariant& details)
    {
        if (!details.isValid())
        {
            return false;
        }

        if (!m_tpShape)
        {
            m_tpShape = new TopoDS_Shape;
        }

        // Lambda for modeling.
        auto modeling = [=](BRepBuilderAPI_MakeShape & cmd)
        {
            // Custom build model.
            //@{
            cmd.Build();
            if (cmd.IsDone())
            {
                TopoDS_Shape shape = cmd.Shape();
                *m_tpShape = shape;
            }
            //@}
        };

        switch (details.toInt())
        {
        case 1:
        {
            // Make Box
            //@{
            BRepPrimAPI_MakeBox cmd(gp_Pnt(0, 0, 0), 1, 1, 1);
            modeling(cmd);
            //@}
            break;
        }
        case 2:
        {
            // Make Cylinder
            //@{
            BRepPrimAPI_MakeCylinder cmd(1000, 500);
            modeling(cmd);
            //@}
            break;
        }
        case 0:
        {
            QString desktop = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);

            // Read STP/IGS/STL
            //@{
            QString filePath = QFileDialog::getOpenFileName(nullptr, tr("Choose STP/IGS/STL file"), "",
                tr("*.stp;*step;*.igs;*.iges;*.stl"));
            if (filePath.isEmpty())
            {
                return false;
            }

            if (filePath.toLower().endsWith("stp") || filePath.toLower().endsWith("step"))
            {
                STEPControl_Reader reader;
                Standard_Integer status = reader.ReadFile(QStringToCharA(filePath));
                if (!status)
                {
                    return false;
                }

                reader.TransferRoots();
                TopoDS_Shape shape = reader.OneShape();
                *m_tpShape = shape;
            }
            else if (filePath.toLower().endsWith("igs") || filePath.toLower().endsWith("iges"))
            {
                IGESControl_Reader reader;
                Standard_Integer status = reader.ReadFile(QStringToCharA(filePath));
                if (!status)
                {
                    return false;
                }

                reader.TransferRoots();
                TopoDS_Shape shape = reader.OneShape();
                *m_tpShape = shape;
            }
            else if (filePath.toLower().endsWith("stl"))
            {
                TopoDS_Shape shape;
                StlAPI::Read(shape, QStringToCharA(filePath));
                *m_tpShape = shape;
            }
            //@}

            break;
        }
        default:
            return false;
        }

        if (!m_tpShape)
        {
            return false;
        }

        // Create shape indice map.
        m_indiceMap = new _ShapeIndiceMap;

        if (m_useIndexMap)
        {
            initShapeIndiceMap(*m_tpShape, m_indiceMap);
        }

        // Generate the shape interactive objects.
        generateShape();

        return true;
    }

    void FITKOCCGraphObjectModel::generateShape()
    {
        if (!m_tpShape || !_dataObj)
        {
            return;
        }

        // Create the FITKOCCAIS_ColoredShape.
        m_fAisShape = new FITKOCCAIS_ColoredShape(*m_tpShape);
        m_fAisShape->SetTypeOfPresentation(PrsMgr_TypeOfPresentation3d::PrsMgr_TOP_AllView);

        // Save the graph object.
        m_fAisShape->setGraphObject(this);
        m_fAisShape->setDataObjectId(_dataObj->getDataObjectID());

        // Create handle.
        Handle(FITKOCCAIS_ColoredShape) shapeHandle(m_fAisShape);

        // Initialize properties.
        //@{
       // Display mode - AIS_Shaded or AIS_WireFrame
        m_fAisShape->SetDisplayMode(AIS_DisplayMode::AIS_Shaded);
        m_fAisShape->SetMaterial(Graphic3d_MaterialAspect(Graphic3d_NameOfMaterial::Graphic3d_NOM_STONE));

        Quantity_Color edgeColor = FITKOCCCommons::QColorToQuan(FITKOCCCommons::s_edgeColor);
        double lineWidth = FITKOCCCommons::s_modelLineWidth;

        // Initialize line width and default color.
        m_fAisShape->SetWidth(lineWidth);
        m_fAisShape->SetColor(edgeColor);

        // Properties.
        //@{
        Handle(Prs3d_Drawer) properties = m_fAisShape->Attributes();

        // Face properties.
        properties->SetupOwnShadingAspect();
        properties->ShadingAspect()->SetColor(FITKOCCCommons::QColorToQuan(FITKOCCCommons::s_faceColor));

        // Line and wire properties.
        properties->LineAspect()->SetColor(edgeColor);
        properties->WireAspect()->SetColor(edgeColor);
        properties->LineAspect()->SetWidth(lineWidth);
        properties->WireAspect()->SetWidth(lineWidth);

        // Face-line properties.
        properties->SetupOwnFaceBoundaryAspect();
        properties->SetFaceBoundaryDraw(Standard_True);
        properties->FaceBoundaryAspect()->SetColor(edgeColor);
        properties->FaceBoundaryAspect()->SetWidth(lineWidth);

        // Point properties.
        properties->SetupOwnPointAspect();
        properties->SetVertexDrawMode(Prs3d_VertexDrawMode::Prs3d_VDM_All);
        properties->PointAspect()->SetScale(5.);
        properties->PointAspect()->SetTypeOfMarker(Aspect_TypeOfMarker::Aspect_TOM_POINT);
        properties->PointAspect()->SetColor(FITKOCCCommons::QColorToQuan(FITKOCCCommons::s_vertexColor));
        m_fAisShape->SetAttributes(properties);
        //@}
        //@}

        // Add to the interactive object list.
        addInteractiveObj(shapeHandle);

        // Save the shape's hash code.
        m_hashCode = m_tpShape->HashCode(INT_MAX);
    }
}   // namespace Exchange